/** 
 * @function:  内核引导程序
 * @reference: 从零开始编写linux内核--海纳
 * @log:       
 * v0.1  输出"Hello World!"
 * v0.2  加载setup程序
 * ---- boot启动后需要进行硬件检查,而boot只有512B的大小无法容纳这么多逻辑,因此将其放在setup程序中
 */

/** 
 * 1. 定义常量BOOTSEG, 早期8086的寄存器为16位,为了寻址20位地址线,采用了两个寄存器拼接的方式来计算地址
 * 2. 即add=base(r1<<4) + offset(r2),这里0x7C0 << 4 = 0x7c00为BIOS的标准加载地址
 */

SYSSIZE = 0x3000

SETUPLEN = 4

BOOTSEG = 0x7c0

INITSEG = 0x9000

SETUPSEG = 0x9020

SYSSEG = 0x1000

ENDSEG = SYSSEG + SYSSIZE

ROOT_DEV = 0x000

.code16  # 告诉汇编器生成16位代码
.text    # 标识接下来的代码是文本段

.global _start  # 声明_start为全局符号,便于链接器寻找
_start:
    /**
     * BIOS 会自动将引导扇区加载到0x7c00处,因此这里通过跳转基地址0x7c0处执行start2
     * 实际地址为(0x7c0<< 4 + start2)
     */
    jmpl $BOOTSEG, $start2  # 将代码段寄存器(CS)设置为 BOOTSEG,并跳转到 start2 标签

start2:
    /**
     * 因为0x7c00是一个比较低的地址,很容易被覆盖,因此需要先将自己转移到一个比较高的地址
     * step1: 将自身搬移到0x90000处
     */
    movw $BOOTSEG, %ax 
    # ax=0x7c0
    #! 段寄存器不允许直接写入立即数,所以需要先写入到AX寄存器,再通过寄存器复制到段寄存器
    movw %ax, %ds 
    # ds=0x7c0
    movw $INITSEG, %ax 
    # ax=0x9000
    movw %ax, %es 
    # es=0x9000
    movw $256, %cx
    # cx=256
    subw %si, %si # 清空自己,表示将偏移量设置为0
    # si=0
    subw %di, %di # 清空自己,表示将偏移量设置为0
    # si=0

    # 重复执行movsw指令,表示将DS:SI指向的数据,其长度由CX指定,搬移到ES:DI指向的位置
    rep movsw
    # 0x7c00+0 --> 0x90000+0

    # step2: 跳转到新地址里执行bootsect
    jmpl $INITSEG, $go
    # cs=0x9000

go:
    # 经过上面跳转后，cs寄存器被设置为0x9000
    movw %cs, %ax
    # ax=0x9000
    movw %ax, %ds
    # ds=0x9000
    movw %ax, %es
    # es=0x9000
    movw %ax, %ss
    # ss=0x9000
    movw $0xFF00, %sp
    # sp=0xff00

load_setup:
    /** 
     * 使用0x13号中断，加载扇区数据到内存
     * :从0号柱面,0号磁头的第二个扇区开始,连续读4个扇区数据进内存，数据存储到0x90200(es<<4 + bx)处
     * !这里扇区中保存的就是setup程序,因此需要其它方式将setup程序放置到上述指定的位置
     * !通过build程序组装镜像时,第一个扇区存放bootsect,2-5扇区存放setup程序
     */
    movw $0x0000, %dx # dh代表磁头号,dl代表驱动器号:0表示软盘,7表示硬盘
    # dx=0x000
    movw $0x0002, %cx # ch代表柱面号的低8位,cl的0~5位表示开始扇区号,6~7位代表磁道号的高2位
    # cx=0x0002
    movw $0x0200, %bx # es:bx 指定数据缓冲区
    # bx=0x0200
    movb $SETUPLEN, %al # 表示需要读出的扇区数量
    # ax=0x0004
    movb $0x02, %ah # 功能号,0x02表示:读磁盘扇区到内存
    # ax=0x0204
    int $0x13 # 0x13号中断，其参数通过寄存器进行转递
    # 如果上述读取成功,则CF标志不会置位,jnc条件跳转指令，跳转到ok_load_setup
    jnc ok_load_setup
    movw $0x0000, %dx
    # dx=0x0000
    movw $0x0000, %ax # ah为0表示重置磁盘
    # dx=0x0000
    # 如果失败,则通过0x13中断,重置磁盘,然后跳转到load_setup
    int $0x13
    jmp load_setup

ok_load_setup:
    movw $msg, %ax      # 将字符串地址加载到ax寄存器
    movw %ax, %bp       # 将ax的值移动到bp寄存器,bp通常用于基址寻址
    movw $0x01301, %ax  # 设置ax寄存器为0x01301,控制显示方式
    movw $0x0c, %bx     # 文字为红色
    movw $21, %cx       # 字符串长度
    movb $0, %dl        # 设置 DL 寄存器为 0,表示输出光标的位置,dh表示行号,dl表示列号
    int $0x010          # 调用 BIOS 中断 0x10,这是一个视频服务中断,用于显示
    #! 触发中断后,硬件读取上述寄存器的值,实现想要的输出

    jmpl $SETUPSEG, $0
    # cs=0x9020

msg:
    .ascii "Setup has been load"

/**
 * 这里org将数据放置在508的位置,后续的两个world恰好构成个4个字节,组合起来正好是512B,与引导程序的大小完全一致
 */
.org 508  # 将接下来的数据放在偏移量为508的位置
root_dev:
    .word ROOT_DEV
boot_flag:  # 定义了一个引导标志 0xaa55,这是 BIOS 用来识别有效引导扇区的标志
    .word 0xaa55

/**
 * 编译命令:
 * as bootsect.S -o bootsect.o
 * ld -m elf_x86_64 -Ttext 0x0 -s --oformat binary -o linux.img bootsect.o
 * 运行命令:
 * qemu-system-x86_64  -boot a -fda linux.img
 * qemu-system-x86_64 -boot a -fda linux.img 是一个用于启动 QEMU 模拟器的命令行选项,具体含义如下:
 * -boot a:这个选项指定了启动顺序。a 表示从软盘(floppy disk)启动。QEMU 会尝试从软盘启动虚拟机。
 * -fda linux.img:这个选项指定了软盘 A 驱动器(floppy drive A)的镜像文件。linux.img 是一个软盘镜像文件,QEMU 会将其挂载到虚拟机的软盘 A 驱动器上。
 */
